package gu.sql2java.manager.parser;

import gu.sql2java.pagehelper.PageException;
import net.sf.jsqlparser.JSQLParserException;
import net.sf.jsqlparser.parser.CCJSqlParser;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.parser.CCJSqlParserVisitor;
import net.sf.jsqlparser.parser.SimpleNode;
import net.sf.jsqlparser.parser.StringProvider;
import net.sf.jsqlparser.schema.Column;
import net.sf.jsqlparser.statement.Statement;
import net.sf.jsqlparser.statement.select.PlainSelect;
import net.sf.jsqlparser.statement.select.Select;
import net.sf.jsqlparser.statement.select.SelectBody;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Strings.isNullOrEmpty;

import java.lang.reflect.InvocationTargetException;
import java.util.regex.Pattern;

import com.google.common.base.Throwables;

public class ParserSupport {
	private static final CountSqlParser countSqlParser = new CountSqlParser();
	/**
	 * 解析SELECT SQL语句,解析失败或非SELECT语句则抛出异常
	 * @param sql
	 */
	public static Select parseSelect(String sql){
		Statement stmt;
		try {
			stmt = CCJSqlParserUtil.parse(checkNotNull(sql,"sql is null"));
		} catch (JSQLParserException e) {
			throw new PageException(e);
		}
		checkArgument(stmt instanceof Select,"%s is not  SELECT statment",sql);
		Select select = (Select)stmt;
		SelectBody selectBody = select.getSelectBody();
		// 暂时只支持简单的SELECT xxxx FROM ....语句不支持复杂语句如WITH
		checkArgument(selectBody instanceof PlainSelect,"ONLY SUPPORT plain select statement %s",sql);
		return (Select)stmt;
	}
	/**
	 * 解析SELECT SQL语句,解析失败或非SELECT语句则
	 * @param sql
	 */
	public static Select parseSelectUnchecked(String sql){
		try {
			return parseSelect(sql);
		} catch (Exception e) {
			return null;
		}
	}
	/**
	 * 生成 count 查询 SQL,如果{@code select}为空则返回{@code null}
	 * @param select
	 * @param countColumn 列名，默认 0
	 */
	public static String countSql(Select select, String countColumn){
		if(null != select){
			SelectBody selectBody = select.getSelectBody();
			/**
			 * count查询时,忽略SQL语句中的分页语句
			 */
			if(selectBody instanceof PlainSelect){
				PlainSelect plainSelect = (PlainSelect)selectBody;
				// 删除OFFSET设置
				plainSelect.setOffset(null);
				// 删除LIMIT设置
				plainSelect.setLimit(null);
			}
			return countSqlParser.getSmartCountSql(select,isNullOrEmpty(countColumn)? "0" : countColumn);
		}
		return null;
	}
	/**
	 * 生成 count 查询 SQL,如果{@code sql}为空或不是SELECT语句不能生成count语句则返回{@code null}
	 * @param sql
	 * @param countColumn 列名，默认 0
	 */
	public static String countSql(String sql, String countColumn){
		Select select;
		if(!isNullOrEmpty(sql) && null != (select = parseSelectUnchecked(sql))){
			return countSql(select,countColumn);
		}
		return null;
	}

	/**
	 * 实现SQL语句解析,解析成功则返回解析后的{@link Statement}，
	 * 并通过{@code visitor}参数提供基于AST(抽象语法树)的遍历所有节点的能力。
	 * @param sql SQL语句
	 * @param visitor 遍历所有节点的{@link SimpleNode}接口实例，为{@code null}忽略
	 * @param sqlSyntaxNormalizer SQL语句分析转换器，为{@code null}忽略
	 * @throws JSQLParserException 输入的SQL语句有语法错误
	 * @see #parse0(String, CCJSqlParserVisitor, SqlSyntaxNormalizer)
	 */
	public static Statement parse(String sql,CCJSqlParserVisitor visitor, SqlSyntaxNormalizer sqlSyntaxNormalizer) throws JSQLParserException{
	    return parse0(sql,visitor, sqlSyntaxNormalizer).statement;
	}
	/**
	 * 检查原输入语句与解析后的语句的长度是否相等，不相等则代表有--注释，有注入危险，抛出{@link InjectionAttackException}异常
	 * @param input 
	 * @param parsed
	 */
	private static void checkIllegalComment(String input, Statement parsed) {
		String s1 = input.replaceAll("\\s+", "");
		if (s1.endsWith(";")) {
			s1 = s1.substring(0, s1.length() - 1);
		}
		String s2 = parsed.toString().replaceAll("\\s+", "");
		if (s1.length() > s2.length()) {
			throw new InjectionAttackException("ILLEGAL COMMENT " + s1.substring(s2.length()));
		}
	}
    /**
     * 参照{@link CCJSqlParserUtil#parseAST(String)}和{@link CCJSqlParserUtil#parse(String)}实现SQL语句解析,
     * 解析成功则返回解析后的{@link SqlParserInfo}对象，
     * 并通过{@code visitor}参数提供基于AST(抽象语法树)的遍历所有节点的能力。
     * @param sql SQL语句
     * @param visitor 遍历所有节点的{@link SimpleNode}接口实例，为{@code null}忽略
     * @param sqlSyntaxAnalyzer SQL语句分析转换器，为{@code null}忽略
     * @throws JSQLParserException 输入的SQL语句有语法错误
     * @see net.sf.jsqlparser.parser.Node#jjtAccept(CCJSqlParserVisitor, Object)
     */
    public static SqlParserInfo parse0(String sql,CCJSqlParserVisitor visitor, SqlSyntaxNormalizer sqlSyntaxAnalyzer) throws JSQLParserException{
        checkArgument(null != sql,"sql is null");
        boolean allowComplexParsing = CCJSqlParserUtil.getNestingDepth(sql)<=CCJSqlParserUtil.ALLOWED_NESTING_DEPTH;
    
        CCJSqlParser parser = CCJSqlParserUtil.newParser(sql).withAllowComplexParsing(allowComplexParsing);
        Statement stmt;
        try {
            stmt = parser.Statement();
        } catch (Exception ex) {
            throw new JSQLParserException(ex);
        }
		checkIllegalComment(sql, stmt);
        if(null != visitor){
            parser.getASTRoot().jjtAccept(visitor, null);
        }
        if(null != sqlSyntaxAnalyzer){
            stmt.accept(sqlSyntaxAnalyzer.resetChanged());
        }
        return new SqlParserInfo(stmt.toString(), stmt, (SimpleNode) parser.getASTRoot());
    }
    
    /**
	 * 调用{@link CCJSqlParser}解析SQL语句部件返回解析生成的对象,如{@code 'ORDER BY id DESC'}
	 * @param <T>
	 * @param input 
	 * @param method 指定调用的{@link CCJSqlParser}解析方法
	 * @param targetType 返回的解析对象类型
	 * @since 3.18.3
	 */
	public static <T> T parseComponent(String input,String method,Class<T> targetType){
		try {
			CCJSqlParser parser = new CCJSqlParser(new StringProvider(input));
			try {
				return checkNotNull(targetType,"targetType is null").cast(parser.getClass().getMethod(method).invoke(parser));
			} catch (InvocationTargetException e) {
				Throwables.throwIfUnchecked(e.getTargetException());
				throw new RuntimeException(e.getTargetException());
			}
		} catch (IllegalAccessException | NoSuchMethodException | SecurityException e) {
			Throwables.throwIfUnchecked(e);
			throw new RuntimeException(e);
		}
	}
	/**
	 * 调用{@link CCJSqlParser}解析SQL语句部件返回解析生成的对象,
	 * 使用{@code targetType}的类名作为解析方法名
	 * @see #parseComponent(String, String, Class)
	 * @since 3.18.3
	 */
	public static <T> T parseComponent(String input,Class<T> targetType){
		return parseComponent(input,
					checkNotNull(targetType,"targetType is null").getSimpleName(),
					targetType);
	}
	/**
     * 如果{@link Column}没有定义table,且字段名为true/false(不区分大小写)则视为布尔常量
     * @param column
     */
    public static boolean isBoolean(Column column){
        return null != column && null == column.getTable() && 
                Pattern.compile("(true|false)", Pattern.CASE_INSENSITIVE).matcher(column.getColumnName()).matches();
    }
	public static class SqlParserInfo {
	    final String nativeSql;
	    final Statement statement;
	    final SimpleNode simpleNode;
        SqlParserInfo(String nativeSql, Statement statement, SimpleNode simpleNode) {
            this.nativeSql = nativeSql;
            this.statement = statement;
            this.simpleNode = simpleNode;
        }
	}


}
